package fds

class TransactionController {

	static OUTLIER_PERCENT_THRESHOLD = 0.3
	static OUTLIER_DENSITY_THRESHOLD = 3
	
    static allowedMethods = [save: "POST", update: "POST", delete: "POST"]

    def index = {
        redirect(action: "list", params: params)
    }

    def list = {
        params.max = Math.min(params.max ? params.int('max') : 10, 100)
		params.sort="id"
		params.order="desc"
        [transactionInstanceList: Transaction.list(params), transactionInstanceTotal: Transaction.count()]
    }

    def create = {
        def transactionInstance = new Transaction()
        transactionInstance.properties = params
        return [transactionInstance: transactionInstance]
    }

    def save = {
        def transactionInstance = new Transaction(params)
        if (transactionInstance.save(flush: true)) {
            flash.message = "${message(code: 'default.created.message', args: [message(code: 'transaction.label', default: 'Transaction'), transactionInstance.id])}"
            redirect(action: "show", id: transactionInstance.id)
        }
        else {
            render(view: "create", model: [transactionInstance: transactionInstance])
        }
    }

    def show = {
        def transactionInstance = Transaction.get(params.id)
        if (!transactionInstance) {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'transaction.label', default: 'Transaction'), params.id])}"
            redirect(action: "list")
        }
        else {
            [transactionInstance: transactionInstance]
        }
    }

    def edit = {
        def transactionInstance = Transaction.get(params.id)
        if (!transactionInstance) {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'transaction.label', default: 'Transaction'), params.id])}"
            redirect(action: "list")
        }
        else {
            return [transactionInstance: transactionInstance]
        }
    }

    def update = {
        def transactionInstance = Transaction.get(params.id)
        if (transactionInstance) {
            if (params.version) {
                def version = params.version.toLong()
                if (transactionInstance.version > version) {
                    
                    transactionInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'transaction.label', default: 'Transaction')] as Object[], "Another user has updated this Transaction while you were editing")
                    render(view: "edit", model: [transactionInstance: transactionInstance])
                    return
                }
            }
            transactionInstance.properties = params
            if (!transactionInstance.hasErrors() && transactionInstance.save(flush: true)) {
                flash.message = "${message(code: 'default.updated.message', args: [message(code: 'transaction.label', default: 'Transaction'), transactionInstance.id])}"
                redirect(action: "show", id: transactionInstance.id)
            }
            else {
                render(view: "edit", model: [transactionInstance: transactionInstance])
            }
        }
        else {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'transaction.label', default: 'Transaction'), params.id])}"
            redirect(action: "list")
        }
    }

    def delete = {
        def transactionInstance = Transaction.get(params.id)
        if (transactionInstance) {
            try {
                transactionInstance.delete(flush: true)
                flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'transaction.label', default: 'Transaction'), params.id])}"
                redirect(action: "list")
            }
            catch (org.springframework.dao.DataIntegrityViolationException e) {
                flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'transaction.label', default: 'Transaction'), params.id])}"
                redirect(action: "show", id: params.id)
            }
        }
        else {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'transaction.label', default: 'Transaction'), params.id])}"
            redirect(action: "list")
        }
    }
	
	// verficacion de fraude
	
	def verify = {
		def transactionInstance = Transaction.get(params.id)
		
		// Algortimo de deteccion de fraude
		
		// 1er paso: Rule Based Filter
		int ruleFilterResult = applyRuleBasedFilter(transactionInstance)
		
		if (ruleFilterResult == 1){
			transactionInstance.status="FRAUD"
			transactionInstance.comments="ADDRESS MISSMATCH"
		} else if (ruleFilterResult == 2){
			transactionInstance.status="FRAUD"
			transactionInstance.comments="OUTLIER"
		} else {
		
			def idPast = isPastFraud(transactionInstance)
			if (idPast!=0){
				transactionInstance.status="FRAUD"
				transactionInstance.comments="SIMILAR:"+idPast
			} else{
				transactionInstance.status="GENUINE"
			}
			
		}
		
        if (transactionInstance.save(flush: true)) {
            flash.message = "${message(code: 'Transaction verified', args: [message(code: 'transaction.label', default: 'Transaction'), transactionInstance.id])}"
            redirect(action: "show", id: transactionInstance.id)
        }
        else {
            flash.message = "${message(code: 'Error', args: [message(code: 'transaction.label', default: 'Transaction'), transactionInstance.id])}"
            redirect(action: "show", id: transactionInstance.id)
        }
	}
	
	
	/**
	 * 
	 * @param tx
	 * @return 1 si billingAddr = shippingAddr => GENUINE
	 */
	def applyRuleBasedFilter(Transaction tx){
		
		// R1: address mismatch
		if (checkAddressMismatch(tx)){
			return 1
		} else if (isOutlier(tx)){
			//R2: Outlier detection basado en el amount de la tx
			return 2;
		} 
		
	}
	
	/**
	 * 
	 * @param tx
	 * @return true si shipping address != billing address
	 */
	def checkAddressMismatch(Transaction tx){
		return tx.card.billingAddress != tx.shippingAddress;
	}
	
	/**
	 * Una tx se considera outlier si su monto se aleja en un x% de otras transacciones del cliente
	 * @param tx
	 * @return
	 */
	def isOutlier(Transaction tx){
		
		// calcular el radio de interes
		def upperLimit =  tx.amount + (tx.amount * OUTLIER_PERCENT_THRESHOLD);
		def lowerLimit =  tx.amount - (tx.amount * OUTLIER_PERCENT_THRESHOLD);
		
		// verficar si existen mas de x transacciones genuinas en ese rango
		def c = Transaction.createCriteria()
		def txMatch = c {
			eq("card", tx.card)
			and {eq("status", "GENUINE")} 
			and {between("amount", lowerLimit, upperLimit)}
		}
		return txMatch.size() < OUTLIER_DENSITY_THRESHOLD
		
	}
	
	def isPastFraud(Transaction tx){
		
		
		// verficar si existen mas de x transacciones genuinas en ese rango
		def c = Transaction.createCriteria()
		def txMatch = c {
			eq("status", "FRAUD")
		}
		def upperLimit =  tx.amount + (tx.amount * OUTLIER_PERCENT_THRESHOLD);
		def lowerLimit =  tx.amount - (tx.amount * OUTLIER_PERCENT_THRESHOLD);
		
		
		for (oneTx in txMatch){
			
			if ( (tx.concept == oneTx.concept) && (oneTx.amount > lowerLimit && oneTx.amount < upperLimit)){
				return oneTx.id	
			}
			
		 }
		return 0
	}
	
	def fdsAlgorithm = {
		
		card,amount,billingAddress,shoppingAddress,e,minPoints,lt,ut,p ->
		
		def pr1 = takepr1(billingAddress,shoppingAddress)
		def pr2 = takepr2(amount,e,minPoints)
		def ph = ds_add(pr1,pr2)
		if (ph < lt) return "GENUINE"
		else if (ph > ut){
			if (confirmFraud){
				requestBlockCard(card)
			}
			 return "FRAUD"
		}
		else if (!exists(card)){
			def phi = ph
			pullAsSuspect(card,phi)
		}
		else {
			def E = find_event(phi)
			def Eh = getEventFraudProb(E)
			def Enoth = getEventNoFraudProb(E)
			def laterH = pbelief(phi,1-phi,Eh,Enoth)
			def laterNoth = pbelief(phi,1-phi,Eh,Enoth)
			if (laterH > laterNoth) {
				A1[0] = ph 
				A1[1] = 0
				A2[2] = 1 - ph
				A2[0] = laterH
				A2[1] = 0
				A2[2] = 1- laterH
				def W= ds_add(A1[], A2[]) 
				Insert_Suspect_Table(card, W)
				if (W< hLT) {
					Delete_Suspect_Table(card)
					return "GENUINE"
				}	
			}
			else if (W> hUT) {
				return "FRAUD"
			}
		}
	}
	
}
